using System;
using System.IO;

namespace System.Windows.Media.DirectShow.Vorbis {
	
	public class CodeBook {
	
	   private HuffmanNode huffmanRoot;
	   private int dimensions, entries;
	
	   private int[] entryLengths;
	   private float[,] valueVector;
	
	   public CodeBook(IBitStream source) {
	
	      // check sync
	      if(source.ReadInt32(24)!=0x564342) {
	         throw new VorbisFormatException("The code book sync pattern is not correct.");
	      }
	
	      dimensions=source.ReadInt32(16);
	      entries=source.ReadInt32(24);
	
	      entryLengths=new int[entries];
	
	      bool ordered=source.ReadBit();
	
	      //System.out.println("ordered: "+ordered);
	
	      if(ordered) {
	         int cl=source.ReadInt32(5)+1;
	         //System.out.println("cl: "+cl);
	         for(int i=0; i<entryLengths.Length; ) {
	            int num=source.ReadInt32(Util.Ilog(entryLengths.Length-i));
	            if(i+num>entryLengths.Length) {
	               //System.out.println("num: "+num);
	               throw new VorbisFormatException("The codebook entry length list is longer than the actual number of entry lengths.");
	            }
	            for(int j=i; j<i+num; j++) {
	            	entryLengths[j]=cl;	
	            }
	            //Arrays.Fill(entryLengths, i, i+num, cl);
	            cl++;
	            i+=num;
	         }
	      }
	      else {
	         // !ordered
	         bool sparse=source.ReadBit();
	
	         //System.out.println("sparse: "+sparse);
	
	         if(sparse) {
	            for(int i=0; i<entryLengths.Length; i++) {
	               if(source.ReadBit()) {
	                  entryLengths[i]=source.ReadInt32(5)+1;
	               }
	               else {
	                  entryLengths[i]=-1;
	               }
	            }
	         }
	         else {
	            // !sparse
	            for(int i=0; i<entryLengths.Length; i++) {
	               entryLengths[i]=source.ReadInt32(5)+1;
	            }
	         }
	      }
	
	      if (!createHuffmanTree(entryLengths)) {
	         throw new VorbisFormatException("An exception was thrown when building the codebook Huffman tree.");
	      }
	
	      int codeBookLookupType=source.ReadInt32(4);
	
	      switch(codeBookLookupType) {
	      case 0:
	         // codebook has no scalar vectors to be calculated
	         break;
	      case 1:
	      case 2:
	         float codeBookMinimumValue=Util.Float32Unpack(source.ReadInt32(32));
	         float codeBookDeltaValue=Util.Float32Unpack(source.ReadInt32(32));
	
	         uint codeBookValueBits=(uint)source.ReadInt32(4)+1;
	         bool codeBookSequenceP=source.ReadBit();
	
	         int codeBookLookupValues=0;
	
	         if(codeBookLookupType==1) {
	            codeBookLookupValues=Util.Lookup1Values(entries, dimensions);
	         }
	         else {
	            codeBookLookupValues=entries*dimensions;
	         }
	
	         int[] codeBookMultiplicands=new int[codeBookLookupValues];
	
	         for(int i=0; i<codeBookMultiplicands.Length; i++) {
	            codeBookMultiplicands[i]=source.ReadInt32(codeBookValueBits);
	         }
	
	         valueVector=new float[entries,dimensions];
	
	         if(codeBookLookupType==1) {
	            for(int i=0; i<entries; i++) {
	               float last=0;
	               int indexDivisor=1;
	               for(int j=0; j<dimensions; j++) {
	                  int multiplicandOffset=
	                     (i/indexDivisor)%codeBookLookupValues;
	                  valueVector[i,j]=
	                     codeBookMultiplicands[multiplicandOffset]*codeBookDeltaValue+codeBookMinimumValue+last;
	                  if(codeBookSequenceP) {
	                     last=valueVector[i,j];
	                  }
	                  indexDivisor*=codeBookLookupValues;
	                  //System.out.println("valueVector["+i+"]["+j+"]: "+valueVector[i][j]);
	               }
	            }
	         }
	         else {
	            throw new Exception();
	            /** @todo implement */
	         }
	         break;
	      default:
	         throw new VorbisFormatException("Unsupported codebook lookup type: "+codeBookLookupType);
	      }
	   }
	
	   private bool createHuffmanTree(int[] entryLengths) {
	      huffmanRoot=new HuffmanNode();
	      for(int i=0; i<entryLengths.Length; i++) {
	         int el=entryLengths[i];
	         if(el>0) {
	            if(!huffmanRoot.SetNewValue(el, i)) {
	               return false;
	            }
	         }
	      }
	      return true;
	   }
	
	   public int Dimensions {
	   	get {
	      	return dimensions;
	      }
	   }
	
	   public int Entries {
	   	get {
	      	return entries;
	      }
	   }
	
	   //public float[] readVQ(ReadableBitChannel source) throws IOException {
	   //   return valueVector[readInt(source)];
	   //}
	
	   public int ReadInt32(IBitStream source) {
	      HuffmanNode node;
	      for(node=huffmanRoot; node.Value==-1; node=source.ReadBit()?node.o1:node.o0);
	      return node.Value;
	   }
	
	   public void ReadVvAdd(float[][] a, IBitStream source, int offset, int length) {
	
	      int i,j;//k;//entry;
	      int chptr=0;
	      int ch=a.Length;
	
	      if(ch==0) {
	         return;
	      }
	
	      int lim=(offset+length)/ch;
	
	      for(i=offset/ch;i<lim;){
	      	int ix=ReadInt32(source);
	         for(j=0;j<dimensions;j++){
	            a[chptr++][i]+=valueVector[ix, j];//=valuelist[t+j];
	            if(chptr==ch){
	               chptr=0;
	               i++;
		         }
	         }
	      }
	   }
	

	   public class HuffmanNode {
	
	      private HuffmanNode parent;
	      private int depth=0;
	      public HuffmanNode o0, o1;
	      private int value=-1;
	      private bool full=false;
	
	      public HuffmanNode():this(null) {
	      }
	
	      public HuffmanNode(HuffmanNode parent) {
	         this.parent=parent;
	         if(parent!=null) {
	            depth=parent.Depth+1;
	         }
	      }
	
	      public HuffmanNode(HuffmanNode parent, int value):this(parent) {
	         this.value=value;
	         full=true;
	      }
	
	      public HuffmanNode Get0() {
	         return o0==null?Set0(new HuffmanNode(this)):o0;
	      }
	
	      public HuffmanNode Get1() {
	         return o1==null?Set1(new HuffmanNode(this)):o1;
	      }
	
	      public int Value {
	      	get {
	         	return value;
	         }
	         set {
	         	full=true;
	         	this.value=value;	
	         }
	      }
	
	      public HuffmanNode Parent {
	      	get {
	         	return parent;
	         }
	      }
	
	      public int Depth {
	      	get {
	         	return depth;
	         }
	      }
	
	      public bool IsFull() {
	         return full?true:(full=o0!=null&&o0.IsFull()&&o1!=null&&o1.IsFull());
	      }
	
	      public HuffmanNode Set0(HuffmanNode value) {
	         return o0=value;
	      }
	
	      public HuffmanNode Set1(HuffmanNode value) {
	         return o1=value;
	      }
	
	      public bool SetNewValue(int depth, int value) {
	         if(IsFull()) {
	            return false;
	         }
	         if(depth==1) {
	            if(o0==null) {
	               Set0(new HuffmanNode(this, value));
	               return true;
	            }
	            else if(o1==null) {
	               Set1(new HuffmanNode(this, value));
	               return true;
	            }
	            else {
	               return false;
	            }
	         }
	         else {
	            return Get0().SetNewValue(depth-1, value)?
	               true:
	               Get1().SetNewValue(depth-1, value);
	         }
	      }
	   }
	
	}		
	
	
}